using System;
using System.IO;

using Microsoft.WowAddonStudio.Lua.AST;

namespace Microsoft.WowAddonStudio.Lua.Parser
{
    /// <summary>
    /// A <see cref="ShiftReduceParser{YYSTYPE,YYLTYPE}"/> for the Lua language.
    /// </summary>
    public partial class Parser
    {
        /// <summary>
        /// Gets or sets the parser sink.
        /// </summary>
        public IParserSink Sink { get; set; }

        /// <summary>
        /// Gets or sets the chunk parsed.
        /// </summary>
        public Chunk Chunk { get; private set; }

        /// <summary>
        /// Set the source to be parsed.
        /// </summary>
        /// <param name="source">The source.</param>
        public void SetSource(string source)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            var scanner = new Scanner();
            scanner.SetSource(source, 0);
            Scanner = scanner;
        }

        /// <summary>
        /// Sets the source to be parsed.
        /// </summary>
        /// <param name="sourceStream">The stream that contains the source.</param>
        public void SetSource(Stream sourceStream)
        {
            if (sourceStream == null)
                throw new ArgumentNullException("sourceStream");

            Scanner = new Scanner(sourceStream);
        }

        /// <summary>
        /// Returns the node at the given location.
        /// </summary>
        /// <param name="line">The index of the line.</param>
        /// <param name="column">The index of the column.</param>
        /// <returns>An instance of the <see cref="Node"/> class, if found; null otherwise.</returns>
        public Node GetNodeAt(int line, int column)
        {
            if (Chunk != null)
                return Chunk.GetNodeAt(line, column);

            return null;
        }

        /// <summary>
        /// Enters into a new lexical scope. See <see cref="ScopedParser{TScope}"/> for more
        /// details.
        /// </summary>
        protected virtual void EnterScope()
        {
        }

        /// <summary>
        /// Leaves the current lexical scope.
        /// </summary>
        protected virtual void LeaveScope()
        {
        }

        /// <summary>
        /// Defines the last scope's location in the current context.
        /// </summary>
        /// <param name="location">The location of the scope.</param>
        protected virtual void DefineScope(LexLocation location)
        {
        }

        private void DefineScope(LexLocation left, LexLocation right)
        {
            this.DefineScope(Merge(left, right));
        }

        /// Append the right node to the left into a single linked list.
        /// </summary>
        /// <param name="left">The left node.</param>
        /// <param name="right">The right node.</param>
        /// <returns>The first node of a single linked list of nodes.</returns>
        private static Node AppendNode(Node left, Node right)
        {
            if (left == null)
                return right;
            if (right == null)
                return left;

            Node last = left;

            while (last.Next != null)
            {
                if (last == right)
                    throw new InvalidOperationException("Cannot append nodes, result would be circular.");

                last = last.Next;
            }

            last.Next = right;

            return left;
        }

        /// <summary>
        /// Append the nodes into a single linked list.
        /// </summary>
        /// <param name="nodes">The nodes to append.</param>
        /// <returns>The first node of a single linked list of nodes.</returns>
        private static Node AppendNodes(params Node[] nodes)
        {
            Node node = nodes[0];

            for (int i = 1; i < nodes.Length; i++)
            {
                node = AppendNode(node, nodes[i]);
            }

            return node;
        }

        private static LexLocation Merge(LexLocation left, LexLocation right)
        {
            return LexLocation.Merge(left, right);
        }

        public static TParser CreateParser<TParser>(Stream stream) where TParser : Parser, new()
        {
            if (stream == null)
                throw new ArgumentNullException("stream");

            // Create a parser
            var parser = new TParser();

            // Set the source to the stream
            parser.SetSource(stream);

            return parser;
        }

        public static TParser CreateParser<TParser>(string source) where TParser : Parser, new()
        {
            if (source == null)
                throw new ArgumentNullException("source");

            // Create a parser
            var parser = new TParser();

            // Set the source to the source
            parser.SetSource(source);

            return parser;
        }

        public static Chunk Parse(Stream stream)
        {
            // Create a parser
            var parser = CreateParser<Parser>(stream);

            // Parse the source
            parser.Parse();

            // Return the parsed chunk
            return parser.Chunk;
        }

        public static Chunk Parse(string source)
        {
            // Create a parser
            var parser = CreateParser<Parser>(source);

            // Parse the source
            parser.Parse();

            // Return the parsed chunk
            return parser.Chunk;
        }

        #region Parser Sink Shortcut Methods

        private void Warning(LexLocation location, string message)
        {
            if (Sink != null)
                Sink.AddWarning(location, message);
        }

        private void Error(LexLocation location, string message)
        {
            if (Sink != null)
                Sink.AddError(location, message);
        }

        private void Match(LexLocation left, LexLocation right)
        {
            if (Sink != null)
                Sink.MatchPair(left, right);
        }

        private void Region(LexLocation location)
        {
            if (Sink != null && Sink.HiddenRegions)
            {
                Sink.AddHiddenRegion(location);
                Sink.ProcessHiddenRegions = true;
            }
        }

        private void Region(LexLocation left, LexLocation right)
        {
            if (Sink != null && Sink.HiddenRegions)
            {
                Region(Merge(left, right));
                Sink.ProcessHiddenRegions = true;
            }
        }

        private void StartName(LexLocation location, string name)
        {
            if (Sink != null/* && Sink.FindNames */)
                    Sink.StartName(location, name);
        }

        private void QualifyName(LexLocation selectorLocation, LexLocation nameLocation, string name)
        {
            if (Sink != null/* && Sink.FindNames */)
                Sink.QualifyName(selectorLocation, nameLocation, name);
        }

        private void StartParameters(LexLocation location)
        {
            if (Sink != null && Sink.MethodParameters)
                Sink.StartParameters(location);
        }

        private void Parameter(LexLocation location)
        {
            if (Sink != null && Sink.MethodParameters)
                Sink.NextParameter(location);
        }

        private void EndParameters(LexLocation location)
        {
            if (Sink != null && Sink.MethodParameters)
                Sink.EndParameters(location);
        }

        #endregion
    }
}